/*
 * <one line to give the program's name and a brief idea of what it does.>
 * Copyright (C) 2019
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef DATABASELMDB_H
#define DATABASELMDB_H

namespace spdlog {
	class logger;
}
struct MDB_env;
struct MDB_txn;

#include "db.hpp"
#include <vector>
#include <string>
#include <memory>
#include <mutex>
#include <map>

typedef std::pair<std::string, MDB_env *> envData;

/**
 * \class LMDB
 * This class using for work lmbd environment
 * Open one environment and use it
 */
class LMDB {
public:
	LMDB() = delete;
	LMDB(const LMDB & other) = delete;

	/**
	 * Open environment, set some options.
	 * This function must be called when connection creating
	 * \return pointer to opened environment or nullptr if have error
	 */
	static MDB_env * init(const std::string & path);

	/**
	 * This function must be called when connection closed
	 */
	static void shutdown(MDB_env * env);

	/**
	 * Convert error code to message
	 * \param[in] error error code
	 * \return error message
	 */
	static std::string errorMessage(const int error);

	static std::shared_ptr<spdlog::logger> Log;								/**< pointer to logger */
private:
	static std::map<envData, uint8_t > m_Environments;					/**< opened environments */
};

/**
 * \class LMTxn
 * RAII style class for LMDB transactions
 */
class LMTxn {
public:
	LMTxn(MDB_env * environment);
	LMTxn(const LMTxn & other) = delete;
	~LMTxn();

	/**
	 * Commit transaction
	 */
	void commit();

	/**
	 * Abort transaction
	 */
	void abort();

	/**
	 * Get raw pointer to MDB_txn
	 * \return raw pointer to MDB_txb object
	 */
	MDB_txn * raw() {
		return m_Transaction;
	}

	/**
	 * Check if transaction is ready and can be used
	 * \return true if transaction ready, false if not
	 */
	bool ready() {
		return m_Transaction != nullptr;
	}

private:
	MDB_txn * m_Transaction = nullptr;
};

/**
 * \class LMDBi
 * RAII style for MDB_dbi
 */
class LMDBi {
public:
	LMDBi(LMTxn * txn, unsigned int flags = 0);
	~LMDBi() {}

	/**
	 * Drop or delete database
	 * \param txn LMTxn pointer
	 * \param[in] close if true database will be close, if false only empty
	 */
	void erase(LMTxn * txn, bool close = false);

	/**
	 * Check if database opened
	 */
	bool open() {
		return m_DBi != 0;
	}

	/**
	 * Return raw handle id
	 */
	unsigned int raw() {
		return m_DBi;
	}

private:
	unsigned int m_DBi = 0;

};

/**
 * \class DBLMDB
 * Working with lmdb database
 */
class DBLMDB : public DataBase {
public:
	DBLMDB(const std::string & dbname);
	DBLMDB(const DBLMDB & other) = delete;
	virtual ~DBLMDB();

	bool empty() override;
	void erase() override;
	bool get(void * data, const std::size_t & size) override;
	void put(void * data, const std::size_t & size) override;
	std::size_t size() override;

	std::size_t addBuilds(std::vector< PathData > * data, PathData * build, DataBase * queue) override;
	bool updateBuild(PathData * data) override;

	bool getBuild(PathData * data);
	std::size_t removeZeroWins();

private:
	MDB_env * m_Env = nullptr;												/**< pointer to opened environment */
	std::mutex m_Mutex;														/**< for multithread work */
};

#endif // DATABASELMDB_H
